﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;

namespace CopyPasteComponents.Editor
{
    public class CopyPasteComponents
    {
        static Component[] copiedComponentCaches;
        static Dictionary<GameObject, Component[]> pastedComponentCaches = new Dictionary<GameObject, Component[]>();
        static ComponentsIgnoreList componentsIgnoreList;
        static List<Type> ignoreComponentTypes = new List<Type>();

        [InitializeOnLoadMethod]
        static void Init()
        {
            componentsIgnoreList = Resources.Load<ComponentsIgnoreList>("ComponentsIgnoreList");
        }

        [MenuItem("GameObject/ComponentsCopy_Paste/Setting", priority = 0)]
        static void Setting()
        {
            EditorGUIUtility.PingObject(componentsIgnoreList);
            Selection.activeObject = componentsIgnoreList;
        }

        #region  组件复制

        /// <summary>
        /// 复制所有组件  对于在忽略列表里面的组件不复制
        /// 复制组件类型 包含自定义的脚本 和 默认组件(如transform)
        /// </summary>
        [MenuItem("GameObject/ComponentsCopy_Paste/Copy All Components", priority = 0)]
        static void CopyAllComponents(MenuCommand menuCommand)
        {
            ignoreComponentTypes = componentsIgnoreList.GetTypes();

            GameObject targetObj = menuCommand.context as GameObject;

            copiedComponentCaches = targetObj.GetComponents<Component>();

            copiedComponentCaches = copiedComponentCaches
                .Where(item => item != null)
                .Where(item => !ignoreComponentTypes.Contains(item.GetType())).ToArray();

            DebugComponentsInfo("Copied Components: ", copiedComponentCaches);

            CopiedNotification("复制完成", $"复制完成:复制{copiedComponentCaches.Length}个脚本", 1);
        }

        /// <summary>
        /// 验证复制组件菜单 如果没有选择对象或者选择多个对象，菜单不能操作
        /// </summary>
        /// <returns></returns>
        [MenuItem("GameObject/ComponentsCopy_Paste/Copy All Components", true)]
        static bool ValidateCopyAllComponents()
        {
            if (Selection.activeTransform == null || Selection.transforms.Length > 1)
                return false;
            return true;
        }

        /// <summary>
        /// 复制Mono组件 对于在忽略列表里面的组件不复制
        /// 复制组件类型 包含自定义的脚本
        /// </summary>
        [MenuItem("GameObject/ComponentsCopy_Paste/Copy Mono Components", priority = 1)]
        static void CopyMonoComponents(MenuCommand menuCommand)
        {
            ignoreComponentTypes = componentsIgnoreList.GetTypes();

            GameObject targetObj = menuCommand.context as GameObject;

            copiedComponentCaches = targetObj.GetComponents<MonoBehaviour>();

            copiedComponentCaches = copiedComponentCaches
                .Where(item => item != null)
                .Where(item => !ignoreComponentTypes.Contains(item.GetType())).ToArray();

            DebugComponentsInfo("Copied Components: ", copiedComponentCaches);

            CopiedNotification("复制完成", $"复制完成:复制{copiedComponentCaches.Length}个脚本", 1);
        }

        /// <summary>
        /// 验证复制组件菜单 如果没有选择对象或者选择多个对象，菜单不能操作
        /// </summary>
        /// <returns></returns>
        [MenuItem("GameObject/ComponentsCopy_Paste/Copy Mono Components", true)]
        static bool ValidateCopyMonoComponents()
        {
            if (Selection.activeTransform == null || Selection.transforms.Length > 1)
                return false;
            return true;
        }

        /// <summary>
        /// 复制内建组件 对于在忽略列表里面的组件不复制
        /// 复制组件类型 Unity内建组件
        /// </summary>
        [MenuItem("GameObject/ComponentsCopy_Paste/Copy Built-in Components", priority = 2)]
        static void CopyBuiltInComponents(MenuCommand menuCommand)
        {
            ignoreComponentTypes = componentsIgnoreList.GetTypes();

            GameObject targetObj = menuCommand.context as GameObject;

            copiedComponentCaches = targetObj.GetComponents<Component>();

            copiedComponentCaches = copiedComponentCaches
                .Where(item => item != null)
                .Where(item => !typeof(MonoBehaviour).IsAssignableFrom(item.GetType()))
                .Where(item => !ignoreComponentTypes.Contains(item.GetType())).ToArray();

            DebugComponentsInfo("Copied Components: ", copiedComponentCaches);

            CopiedNotification("复制完成", $"复制完成:复制{copiedComponentCaches.Length}个脚本", 1);
        }

        /// <summary>
        /// 验证复制组件菜单 如果没有选择对象或者选择多个对象，菜单不能操作
        /// </summary>
        /// <returns></returns>
        [MenuItem("GameObject/ComponentsCopy_Paste/Copy Built-in Components", true)]
        static bool ValidateCopyBuiltInComponents()
        {
            if (Selection.activeTransform == null || Selection.transforms.Length > 1)
                return false;
            return true;
        }

        #endregion

        #region 组件粘贴

        /// <summary>
        /// 粘贴组件 不序列化 仅粘贴脚本不赋值
        /// </summary>
        /// <param name="menuCommand"></param>
        [MenuItem("GameObject/ComponentsCopy_Paste/Paste Components Additional_NoSerialize", priority = 13)]
        static void PasteComponents_Additional_NoSerialize(MenuCommand menuCommand)
        {
            GameObject targetObj = menuCommand.context as GameObject;

            Component[] newAddComponents = PasteComponents_Additional(targetObj, false, out List<Component> ignoreComponents);

            StorePastedComponent(targetObj, newAddComponents);

            DebugComponentsInfo("Pasted Components: ", newAddComponents);

            DebugComponentsInfo("Ignore Components: ", ignoreComponents.ToArray());

            CopiedNotification("粘贴完成", $"粘贴完成:粘贴{newAddComponents.Length}个脚本", 1);
        }

        /// <summary>
        /// 验证粘贴组件菜单 如果没有选择对象或则没有复制组件，菜单不能操作
        /// </summary>
        /// <returns></returns>
        [MenuItem("GameObject/ComponentsCopy_Paste/Paste Components Additional_NoSerialize", true)]
        static bool ValidatePasteComponents_Additional_NoSerialize()
        {
            if (Selection.activeTransform == null || copiedComponentCaches == null)
                return false;
            return true;
        }

        /// <summary>
        /// 粘贴组件  序列化 粘贴脚本并赋值
        /// </summary>
        /// <param name="menuCommand"></param>
        [MenuItem("GameObject/ComponentsCopy_Paste/Paste Components Additional_Serialize", priority = 14)]
        static void PasteComponents_Additional_Serialize(MenuCommand menuCommand)
        {
            GameObject targetObj = menuCommand.context as GameObject;

            Component[] newAddComponents = PasteComponents_Additional(targetObj, true, out List<Component> ignoreComponents);

            StorePastedComponent(targetObj, newAddComponents);

            DebugComponentsInfo("Pasted Components: ", newAddComponents);

            DebugComponentsInfo("Ignore Components: ", ignoreComponents.ToArray());

            CopiedNotification("粘贴完成", $"粘贴完成:粘贴{newAddComponents.Length}个脚本", 1);
        }

        /// <summary>
        /// 验证粘贴组件菜单 如果没有选择对象或则没有复制组件，菜单不能操作
        /// </summary>
        /// <returns></returns>
        [MenuItem("GameObject/ComponentsCopy_Paste/Paste Components Additional_Serialize", true)]
        static bool ValidatePasteComponents_Additional_Serialize()
        {
            if (Selection.activeTransform == null || copiedComponentCaches == null)
                return false;
            return true;
        }

        /// <summary>
        /// 粘贴组件 附加模式  在不改变原有组件的情况下，将复制的脚本粘贴到目标对象上，不对原有组件进行刷新
        /// 在粘贴组件的时候是否考虑对于不允许多次挂载的组件进行过滤
        /// </summary>
        /// <param name="menuCommand"></param>
        /// <param name="isSerialize"></param>
        static Component[] PasteComponents_Additional(GameObject targetObj, bool isSerialize, out List<Component> ignoreComponents)
        {
            Undo.RecordObject(targetObj, "PasteComponents_Additional");

            List<Component> newAddComponents = new List<Component>();

            ignoreComponents = new List<Component>();

            for (int i = 0; i < copiedComponentCaches.Length; i++)
            {
                Component[] targetComs = targetObj.GetComponents(copiedComponentCaches[i].GetType());

                //对于新添加的组件，需要判断该组件是否允许多次添加，
                //如果允许直接添加，如果不允许，判断是否已经存在，如果存在，则不粘贴，如果不存在，则粘贴

                if (targetComs == null || !HasDisallowMultipleComponent(copiedComponentCaches[i].GetType()))
                {
                    if (isSerialize)
                    {
                        ComponentUtility.CopyComponent(copiedComponentCaches[i]);

                        ComponentUtility.PasteComponentAsNew(targetObj);
                    }
                    else
                    {
                        Undo.AddComponent(targetObj, copiedComponentCaches[i].GetType());
                    }
                }
                else
                {
                    ignoreComponents.Add(copiedComponentCaches[i]);
                }

                Component newAddCom = GetNewAddComponents(targetObj);

                if (!newAddComponents.Contains(newAddCom))
                    newAddComponents.Add(newAddCom);
            }

            Scene currentScene = EditorSceneManager.GetActiveScene();
            EditorSceneManager.MarkSceneDirty(currentScene);

            return newAddComponents.ToArray();
        }

        /// <summary>
        /// 粘贴组件 
        /// </summary>
        /// <param name="menuCommand"></param>
        [MenuItem("GameObject/ComponentsCopy_Paste/Paste Components Override_NoSerialize", priority = 15)]
        static void PasteComponents_Override_NoSerialize(MenuCommand menuCommand)
        {
            GameObject targetObj = menuCommand.context as GameObject;

            Component[] newAddComponents = PasteComponents_Override(targetObj, false, out List<Component> ignoreComponents);

            StorePastedComponent(targetObj, newAddComponents);

            DebugComponentsInfo("Pasted Components: ", newAddComponents);

            DebugComponentsInfo("Ignore Components: ", ignoreComponents.ToArray());

            CopiedNotification("粘贴完成", $"粘贴完成:粘贴{newAddComponents.Length}个脚本", 1);
        }

        /// <summary>
        /// 验证粘贴组件菜单 如果没有选择对象或则没有复制组件，菜单不能操作
        /// </summary>
        /// <returns></returns>
        [MenuItem("GameObject/ComponentsCopy_Paste/Paste Components Override_NoSerialize", true)]
        static bool ValidatePasteComponents_Override_NoSerialize()
        {
            if (Selection.activeTransform == null || copiedComponentCaches == null)
                return false;
            return true;
        }

        /// <summary>
        /// 粘贴组件 
        /// </summary>
        /// <param name="menuCommand"></param>
        [MenuItem("GameObject/ComponentsCopy_Paste/Paste Components Override_Serialize", priority = 16)]
        static void PasteComponents_Override_Serialize(MenuCommand menuCommand)
        {
            GameObject targetObj = menuCommand.context as GameObject;

            Component[] newAddComponents = PasteComponents_Override(targetObj, true, out List<Component> ignoreComponents);

            StorePastedComponent(targetObj, newAddComponents);

            DebugComponentsInfo("Pasted Components: ", copiedComponentCaches);

            DebugComponentsInfo("Ignore Components: ", ignoreComponents.ToArray());

            CopiedNotification("粘贴完成", $"粘贴完成:粘贴{copiedComponentCaches.Length}个脚本", 1);
        }

        /// <summary>
        /// 验证粘贴组件菜单 如果没有选择对象或则没有复制组件，菜单不能操作
        /// </summary>
        /// <returns></returns>
        [MenuItem("GameObject/ComponentsCopy_Paste/Paste Components Override_Serialize", true)]
        static bool ValidatePasteComponents_Override_Serialize()
        {
            if (Selection.activeTransform == null || copiedComponentCaches == null)
                return false;
            return true;
        }

        /// <summary>
        /// 粘贴组件 覆盖模式  对已经存在的相同类型的组件进行刷新，对新的组件和多余的同类型的组件进行粘贴
        /// 在粘贴组件的时候是否考虑对于不允许多次挂载的组件进行过滤
        /// </summary>
        /// <param name="menuCommand"></param>
        static Component[] PasteComponents_Override(GameObject targetObj, bool isSerialize, out List<Component> ignoreComponents)
        {
            Undo.RecordObject(targetObj, "PasteComponents_Override");

            List<Component> targetComCaches = new List<Component>();

            List<Component> newAddComponents = new List<Component>();

            ignoreComponents = new List<Component>();

            for (int i = 0; i < copiedComponentCaches.Length; i++)
            {
                ComponentUtility.CopyComponent(copiedComponentCaches[i]);

                //刷新顺序依赖于GetComponents的顺序
                Component[] targetComs = targetObj.GetComponents(copiedComponentCaches[i].GetType());
                Component targetCom = null;

                //剔除已经操作过的组件
                if (targetComs != null)
                    targetCom = targetComs.Where(item => !targetComCaches.Contains(item))?.FirstOrDefault();

                if (targetCom == null)
                {
                    //HasDisallowMultipleComponent()
                    //对于新添加的组件，需要判断该组件是否允许多次添加，
                    //如果允许直接添加，如果不允许，判断是否已经存在，如果存在，则不粘贴，如果不存在，则粘贴

                    if (targetComs == null || !HasDisallowMultipleComponent(copiedComponentCaches[i].GetType()))
                    {
                        if (isSerialize)
                            ComponentUtility.PasteComponentAsNew(targetObj);  // 粘贴新的组件
                        else
                            Undo.AddComponent(targetObj, copiedComponentCaches[i].GetType());
                    }
                    else
                    {
                        ignoreComponents.Add(copiedComponentCaches[i]);
                    }

                    targetCom = GetNewAddComponents(targetObj);

                    if (!newAddComponents.Contains(targetCom))
                        newAddComponents.Add(targetCom);
                }
                else
                {
                    //对已经存在的脚本，不序列化时不做任何处理
                    if (isSerialize)
                        ComponentUtility.PasteComponentValues(targetCom);
                }

                targetComCaches.Add(targetCom);
            }

            Scene currentScene = EditorSceneManager.GetActiveScene();
            EditorSceneManager.MarkSceneDirty(currentScene);

            return newAddComponents.ToArray();
        }

        #endregion

        #region 组件清除

        /// <summary>
        /// 清除已经粘贴的组件  这里仅删除新增的组件，需要保持对象原本的组件
        /// </summary>
        /// <param name="menuCommand"></param>
        [MenuItem("GameObject/ComponentsCopy_Paste/Clear Pasted Components", priority = 28)]
        static void ClearPastedComponents(MenuCommand menuCommand)
        {
            GameObject targetObj = menuCommand.context as GameObject;

            if (pastedComponentCaches.ContainsKey(targetObj))
            {
                Component[] components = pastedComponentCaches[targetObj];

                foreach (var item in components)
                {
                    if (item == null) continue;
                    Undo.DestroyObjectImmediate(item);
                }

                DebugComponentsInfo("Clear Components: ", components);

                CopiedNotification("清除完成", $"清除完成:清除{components.Length}个脚本", 1);

                pastedComponentCaches.Remove(targetObj);
            }
            else
            {
                CopiedNotification("清除失败", $"清除失败:没有粘贴的组件", 1);
            }

            Scene currentScene = EditorSceneManager.GetActiveScene();
            EditorSceneManager.MarkSceneDirty(currentScene);
        }

        /// <summary>
        /// 验证清除已粘贴组件菜单 如果没有选择对象，菜单不能操作
        /// </summary>
        /// <returns></returns>
        [MenuItem("GameObject/ComponentsCopy_Paste/Clear Pasted Components", true)]
        static bool ValidateClearPastedComponents(MenuCommand menuCommand)
        {
            if (Selection.activeTransform == null)
                return false;
            return true;
        }

        /// <summary>
        /// 清除全部脚本  忽略类型不清除
        /// </summary>
        /// <param name="menuCommand"></param>
        [MenuItem("GameObject/ComponentsCopy_Paste/Clear All Components", priority = 29)]
        static void ClearAllComponents(MenuCommand menuCommand)
        {
            ignoreComponentTypes = componentsIgnoreList.GetTypes();

            GameObject targetObj = menuCommand.context as GameObject;

            var components = targetObj.GetComponents<Component>();

            Component[] missingScripts = components.Where(item => item == null).ToArray();

            Component[] normalComponents = components
                .Where(item => item != null)
                .Where(item => !ignoreComponentTypes.Contains(item.GetType())).ToArray();

            if (missingScripts != null && missingScripts.Count() > 0)
            {
                GameObjectUtility.RemoveMonoBehavioursWithMissingScript(targetObj);
                Undo.RecordObject(targetObj, "Remove Missing Script");
            }

            for (int i = 0; i < normalComponents.Length; i++)
            {
                Undo.DestroyObjectImmediate(normalComponents[i]);
            }

            DebugComponentsInfo("Clear Components: ", normalComponents);

            CopiedNotification("清除完成", $"清除完成:清除{normalComponents.Length}个脚本", 1);

            Scene currentScene = EditorSceneManager.GetActiveScene();
            EditorSceneManager.MarkSceneDirty(currentScene);
        }

        /// <summary>
        /// 验证清除组件菜单 如果没有选择对象，菜单不能操作
        /// </summary>
        /// <returns></returns>
        [MenuItem("GameObject/ComponentsCopy_Paste/Clear All Components", true)]
        static bool ValidateClearAllComponents()
        {
            if (Selection.activeTransform == null)
                return false;
            return true;
        }

        /// <summary>
        /// 清除Mono脚本  忽略类型不清除
        /// </summary>
        /// <param name="menuCommand"></param>
        [MenuItem("GameObject/ComponentsCopy_Paste/Clear Mono Components", priority = 30)]
        static void ClearMonoComponents(MenuCommand menuCommand)
        {
            ignoreComponentTypes = componentsIgnoreList.GetTypes();

            GameObject targetObj = menuCommand.context as GameObject;

            var components = targetObj.GetComponents<MonoBehaviour>();

            Component[] missingScripts = components.Where(item => item == null).ToArray();

            Component[] normalComponents = components
                .Where(item => item != null)
                .Where(item => !ignoreComponentTypes.Contains(item.GetType())).ToArray();

            if (missingScripts != null && missingScripts.Count() > 0)
            {
                GameObjectUtility.RemoveMonoBehavioursWithMissingScript(targetObj);
                Undo.RecordObject(targetObj, "Remove Missing Script");
            }

            for (int i = 0; i < normalComponents.Length; i++)
            {
                Undo.DestroyObjectImmediate(normalComponents[i]);
            }

            DebugComponentsInfo("Clear Components: ", normalComponents);

            CopiedNotification("清除完成", $"清除完成:清除{normalComponents.Length}个脚本", 1);

            Scene currentScene = EditorSceneManager.GetActiveScene();
            EditorSceneManager.MarkSceneDirty(currentScene);
        }

        /// <summary>
        /// 验证清除组件菜单 如果没有选择对象，菜单不能操作
        /// </summary>
        /// <returns></returns>
        [MenuItem("GameObject/ComponentsCopy_Paste/Clear Mono Components", true)]
        static bool ValidateClearMonoComponents()
        {
            if (Selection.activeTransform == null)
                return false;
            return true;
        }

        /// <summary>
        /// 清除内建组件  注意 Transform 不能清除，而且这里不进行过滤
        /// </summary>
        /// <param name="menuCommand"></param>
        [MenuItem("GameObject/ComponentsCopy_Paste/Clear Built-in Components", priority = 31)]
        static void ClearBuiltInComponents(MenuCommand menuCommand)
        {
            GameObject targetObj = menuCommand.context as GameObject;

            var components = targetObj.GetComponents<Component>();

            components = components
                .Where(item => item != null)
                .Where(item => !typeof(MonoBehaviour).IsAssignableFrom(item.GetType()))
                .Where(item => item.GetType() != typeof(Transform)).ToArray();

            for (int i = 0; i < components.Length; i++)
            {
                Undo.DestroyObjectImmediate(components[i]);
            }

            DebugComponentsInfo("Clear Components: ", components);

            CopiedNotification("清除完成", $"清除完成:清除{components.Length}个脚本", 1);

            Scene currentScene = EditorSceneManager.GetActiveScene();
            EditorSceneManager.MarkSceneDirty(currentScene);
        }

        /// <summary>
        /// 验证清除组件菜单 如果没有选择对象，菜单不能操作
        /// </summary>
        /// <returns></returns>
        [MenuItem("GameObject/ComponentsCopy_Paste/Clear Built-in Components", true)]
        static bool ValidateClearBuiltInComponents()
        {
            if (Selection.activeTransform == null)
                return false;
            return true;
        }

        /// <summary>
        /// 清除丢失脚本
        /// </summary>
        /// <param name="menuCommand"></param>
        [MenuItem("GameObject/ComponentsCopy_Paste/Clear Missing Components", priority = 32)]
        static void ClearMissingComponents(MenuCommand menuCommand)
        {
            GameObject targetObj = menuCommand.context as GameObject;

            var components = targetObj.GetComponents<MonoBehaviour>();

            Component[] missingScripts = components.Where(item => item == null).ToArray();

            if (missingScripts != null && missingScripts.Count() > 0)
            {
                GameObjectUtility.RemoveMonoBehavioursWithMissingScript(targetObj);
                Undo.RecordObject(targetObj, "Remove Missing Script");
            }

            string msg = $"<color=green>Clear Components: </color>清除完成:清除{missingScripts.Length}个丢失脚本";
            Debug.Log(msg);

            CopiedNotification("清除完成", $"清除完成:清除{missingScripts.Length}个丢失脚本", 1);

            Scene currentScene = EditorSceneManager.GetActiveScene();
            EditorSceneManager.MarkSceneDirty(currentScene);
        }

        /// <summary>
        /// 验证清除组件菜单 如果没有选择对象，菜单不能操作
        /// </summary>
        /// <returns></returns>
        [MenuItem("GameObject/ComponentsCopy_Paste/Clear Missing Components", true)]
        static bool ValidateClearMissingComponents()
        {
            if (Selection.activeTransform == null)
                return false;
            return true;
        }

        #endregion

        #region 组件操作工具方法

        /// <summary>
        /// 打印复制的组件信息
        /// </summary>
        /// <param name="components"></param>
        /// <returns></returns>
        static string DebugComponentsInfo(string dubugTip, Component[] components)
        {
            string msg = $"<color=green>{dubugTip}</color>";

            for (int i = 0; i < components.Length; i++)
            {
                msg += components[i].GetType() + "\n";
            }
            Debug.Log(msg);
            return msg;
        }

        /// <summary>
        /// 显示复制完成提示
        /// </summary>
        static void CopiedNotification(string notifyTitle, string notifyText, float duration)
        {
            SceneView.lastActiveSceneView.ShowNotification(new GUIContent(notifyText, notifyTitle), duration);
            SceneView.RepaintAll();
        }

        /// <summary>
        /// 获取最新添加的组件
        /// </summary>
        /// <param name="targetObj"></param>
        /// <returns></returns>
        static Component GetNewAddComponents(GameObject targetObj)
        {
            // 获取粘贴后的SerializedObject
            SerializedObject serializedObjectAfter = new SerializedObject(targetObj);
            SerializedProperty componentProperty = serializedObjectAfter.FindProperty("m_Component");
            serializedObjectAfter.Update();

            // 获取新添加的组件
            SerializedProperty newComponentProperty = componentProperty.GetArrayElementAtIndex(componentProperty.arraySize - 1);
            SerializedProperty componentReference = newComponentProperty.FindPropertyRelative("component");
            Object componentObject = componentReference.objectReferenceValue;
            return componentObject as Component;
        }

        /// <summary>
        /// 存储粘贴的组件
        /// </summary>
        /// <param name="targetObj"></param>
        /// <param name="component"></param>
        static void StorePastedComponent(GameObject targetObj, Component[] component)
        {
            if (!pastedComponentCaches.ContainsKey(targetObj))
            {
                pastedComponentCaches.Add(targetObj, null);
            }

            pastedComponentCaches[targetObj] = component;
        }

        // 检查给定类型是否应用了DisallowMultipleComponent特性
        static bool HasDisallowMultipleComponent(Type type)
        {
            object[] attributes = type.GetCustomAttributes(typeof(DisallowMultipleComponent), false);
            return attributes.Length > 0;
        }

        #endregion
    }
}
